#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>


#define KEY_CNT 1
#define NAME "key"

struct key_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int key_gpio;
    int key_val;
    struct mutex mutex;
};

static struct key_dev key;


static int key_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int key_release(struct inode *inode, struct file *filp)
{
    return 0;    
}

static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret=0;

    if(mutex_lock_interruptible(&key.mutex)){
        return -ERESTARTSYS;
    }

    if(gpio_get_value(key.key_gpio)==0){
        while(gpio_get_value(key.key_gpio)==0);
        key.key_val=0x0;
    }
    else{
        key.key_val=0xFF;
    }

    ret=copy_to_user(buf, &key.key_val, sizeof(int));

    mutex_unlock(&key.mutex);

    return ret;
}

static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static struct file_operations key_fops={
    .owner=THIS_MODULE,
    .open=key_open,
    .read=key_read,
    .write=key_write,
    .release=key_release,
};

static int __init mykey_init(void)
{
    int ret;
    const char *str;

    /*0*/
    key.nd=of_find_node_by_path("/key");
    ret=of_property_read_string(key.nd, "status", &str);
    if(strcmp(str, "okay")){
        return -EINVAL;
    }

    ret=of_property_read_string(key.nd, "compatible", &str);
    if(strcmp(str, "alientek,key")){
        return -EINVAL;
    }

    printk(KERN_INFO "key device matching successful!\n");

    /*1*/
    key.key_gpio=of_get_named_gpio(key.nd, "key-gpio", 0);
    printk(KERN_INFO "key: key-gpio num=%d\n", key.key_gpio);

    ret=gpio_request(key.key_gpio, "KEY-GPIO");
    gpio_direction_input(key.key_gpio);

    /*7*/
    if(key.major){
        key.devid=MKDEV(key.major,0);
        ret=register_chrdev_region(key.devid, KEY_CNT, NAME);
    }
    else {
        ret=alloc_chrdev_region(&key.devid, 0, KEY_CNT, NAME);
        key.major=MAJOR(key.devid);
        key.minor=MINOR(key.devid);
    }

    printk("key major=%d, minor=%d\n", key.major, key.minor);

    /*8*/
    key.cdev.owner=THIS_MODULE;
    cdev_init(&key.cdev, &key_fops);
    ret=cdev_add(&key.cdev, key.devid, KEY_CNT);

    /*9*/
    key.class=class_create(THIS_MODULE, NAME);
    key.device=device_create(key.class,
                                    NULL,
                                    key.devid,
                                    NULL,
                                    NAME);
    mutex_init(&key.mutex);

    return 0;
}

static void __exit mykey_exit(void)
{
    device_destroy(key.class, key.devid);
    class_destroy(key.class);
    cdev_del(&key.cdev);
    unregister_chrdev_region(key.devid, KEY_CNT);
    gpio_free(key.key_gpio);

    printk("key_exit()\n");
}

module_init(mykey_init);
module_exit(mykey_exit);

MODULE_AUTHOR("xujianchao");
MODULE_DESCRIPTION("xujianchao ZYNQ GPIO KEY Driver");
MODULE_LICENSE("GPL");
